Opérations terminales
forEach
La méthode forEach est une opération terminale très couramment utilisée avec les flux Java (streams). Elle permet d'appliquer une action à chaque élément du flux. Voici une explication détaillée :
Fonctionnement de forEach
- Itération sur les éléments :
forEachparcourt chaque élément du flux et exécute une action spécifiée sur chaque élément.
- Action consommateur :
- L'action est définie par un objet
Consumer, qui est une interface fonctionnelle avec une seule méthodeaccept(T t). Cette méthode prend un élément du flux comme argument et effectue une action sur cet élément.
- L'action est définie par un objet
- Opération terminale :
forEachest une opération terminale, ce qui signifie qu'elle consomme le flux. Une fois queforEacha été appelé, vous ne pouvez plus effectuer d'autres opérations sur ce flux.
- Effets de bord :
forEachest principalement utilisé pour les effets de bord, tels que l'affichage d'éléments, l'écriture dans un fichier ou la modification d'une variable externe. Il n'est pas conçu pour renvoyer une nouvelle collection ou transformer les éléments du flux.
- Ordre :
- Lorsque le stream est séquentiel, forEach garanti de respecter l'ordre d'apparition des éléments.
- Lorsque le stream est parallélisé avec parallelStream, il n'y a aucune garantie sur l'ordre dans lequel les éléments seront traités.
Exemples d'utilisation
-
Affichage des éléments :
List<String> mots = List.of("pomme", "banane", "orange");mots.stream().forEach(System.out::println);Cet exemple affiche chaque mot de la liste sur la console.
-
Accès à une variable externe :
List<String> noms = List.of("Alice", "Bob", "Clara");List<String> resultat = new ArrayList<>();noms.stream().filter(n -> n.length() > 3).forEach(n -> resultat.add(n)); // accès à la variable externe resultatSystem.out.println(resultat); // [Alice, Clara]resultatest accessible car la référence ne change pas — on ne réassigne pasresultat, on modifie seulement son contenu.
Points importants
forEachest utilisé pour effectuer des actions sur chaque élément, mais il ne renvoie pas de résultat.- Il est important de noter que les lambdas utilisées dans forEach ne doivent pas tenter de modifier la source du stream.
- Pour transformer les éléments d'un flux et renvoyer une nouvelle collection, utilisez
mapetcollect. - Pour filtrer les éléments d'un flux, utilisez
filter.
Quand utiliser forEach
- Lorsque vous devez effectuer une action sur chaque élément d'un flux sans renvoyer de résultat.
- Pour les effets de bord, tels que l'affichage, l'écriture dans un fichier ou la modification de variables externes.
BinaryOperator
Un BinaryOperator<T> est une interface fonctionnelle qui prend deux arguments du même type et retourne un résultat du même type.
BinaryOperator<Integer> addition = (a, b) -> a + b;
System.out.println(addition.apply(3, 5)); // 8
BinaryOperator<String> concatenation = (a, b) -> a + " " + b;
System.out.println(concatenation.apply("Bonjour", "monde")); // Bonjour monde
C'est l'interface utilisée par reduce() pour combiner les éléments d'un stream un par un :
[1, 2, 3, 4, 5]
1 + 2 = 3
3 + 3 = 6
6 + 4 = 10
10 + 5 = 15
On peut aussi utiliser une référence de méthode existante :
BinaryOperator<Integer> max = Integer::max;
System.out.println(max.apply(10, 20)); // 20
Reduce
La méthode reduce en Java est une opération terminale puissante utilisée dans la programmation fonctionnelle avec les streams. Elle permet de combiner les éléments d'un flux en une seule valeur. Voici une explication détaillée :
Fonctionnement de reduce
La méthode reduce prend généralement deux arguments :
- Une valeur d'identité (identity) : C'est la valeur initiale de l'accumulation, et c'est aussi le résultat par défaut si le flux est vide.
- Une fonction d'accumulateur : C'est une fonction qui prend deux arguments : la valeur d'accumulation courante et un élément du flux. Elle renvoie une nouvelle valeur d'accumulation.
Voici la signature générale de la méthode reduce :
T reduce(T identity, BinaryOperator<T> accumulator)
Exemples d'utilisation
-
Somme des éléments d'un flux :
List<Integer> nombres = List.of(1, 2, 3, 4, 5);int somme = nombres.stream().reduce(0, (a, b) -> a + b);System.out.println(somme); // Output: 15Dans cet exemple :
0est la valeur d'identité.(a, b) -> a + best la fonction d'accumulateur qui additionne les éléments.
-
Concaténation de chaînes de caractères :
List<String> mots = List.of("Bonjour", "le", "monde");String phrase = mots.stream().reduce("", (a, b) -> a + " " + b);System.out.println(phrase); // Output: Bonjour le mondeIci :
""est la chaîne vide initiale.(a, b) -> a + " " + bconcatène les chaînes avec un espace.
-
Trouver le maximum :
List<Integer> nombres = List.of(5, 2, 8, 1, 9);int max = nombres.stream().reduce(Integer.MIN_VALUE, Integer::max);System.out.println(max); // Output: 9Dans ce cas :
Integer.MIN_VALUEest la valeur d'identité.Integer::maxest une référence de méthode qui trouve le maximum entre deux nombres.
Points importants
reduceest une opération terminale, ce qui signifie qu'elle consomme le flux et produit un résultat.- Il est essentiel de choisir une valeur d'identité appropriée pour garantir un résultat correct.
- La fonction d'accumulateur doit être associative, c'est-à-dire que l'ordre des opérations ne doit pas affecter le résultat.
- lors de l'utilisation de
reduceavec les flux parallèles, il est important que la fonction d'accumulation soit associative et sans effets secondaires.
Avantages de reduce
- Il permet d'exprimer des opérations de réduction de manière concise et lisible.
- Il s'intègre bien avec les autres opérations de flux, ce qui facilite la création de pipelines de traitement de données complexes.
- Il permet d'écrire du code de type fonctionnel, et donc de bénéficier de tous les avantages de ce paradigme de programmation.
Collect
La méthode collect() est une opération terminale des Streams en Java qui sert à rassembler les éléments transformés d’un Stream dans une structure mutable comme une liste, un ensemble, une chaîne de caractères, une map, ou même une valeur agrégée personnalisée.
Elle utilise généralement la classe utilitaire Collectors pour spécifier le type de collecte.
1. Collecter dans une liste
List<String> nomsAvecA = noms.stream()
.filter(n -> n.startsWith("A"))
.collect(Collectors.toList());
2. Collecter dans un ensemble (Set)
Set<String> nomsUniques = noms.stream()
.map(String::toLowerCase)
.collect(Collectors.toSet());
3. Joindre des chaînes
String nomsConcat = noms.stream()
.collect(Collectors.joining(", "));
Résultat : "Alice, Bob, Alex, Charles"
4. Regrouper par clé (groupingBy)
groupingBy regroupe tous les éléments partageant la même clé dans une List. Si plusieurs éléments ont la même clé, ils se retrouvent tous dans la même liste.
List<String> noms = List.of("Alice", "Alex", "Bob", "Baptiste", "Clara", "Charles", "Camille");
Map<Character, List<String>> nomsParPremiereLettre = noms.stream()
.collect(Collectors.groupingBy(n -> n.charAt(0)));
System.out.println(nomsParPremiereLettre);
// {A=[Alice, Alex], B=[Bob, Baptiste], C=[Clara, Charles, Camille]}
Chaque clé ('A', 'B', 'C') pointe vers la liste de tous les noms commençant par cette lettre.
5. Associer clé-valeur (toMap)
List<String> noms = List.of("Alice", "Bob", "Charles");
Map<String, Integer> nomEtLongueur = noms.stream()
.collect(Collectors.toMap(n -> n, String::length));
System.out.println(nomEtLongueur);
// {Alice=5, Bob=3, Charles=7}
Le premier argument est la fonction qui produit la clé, le deuxième celle qui produit la valeur.
En résumé :
collect()est puissant et polyvalent.- Il convertit le Stream en une structure de données utilisable.
- À utiliser chaque fois que tu veux récupérer un résultat à partir d'un Stream.
Count
En programmation fonctionnelle Java, count est une opération terminale utilisée avec les flux (streams) pour compter le nombre d'éléments dans le flux.
Fonctionnement de count
- Comptage des éléments :
countparcourt tous les éléments du flux et renvoie le nombre total d'éléments.
- Opération terminale :
- Comme toutes les opérations terminales,
countconsomme le flux. Une fois quecounta été appelé, vous ne pouvez plus effectuer d'autres opérations sur ce flux.
- Comme toutes les opérations terminales,
- Retourne un
long:- La méthode
countrenvoie une valeur de typelong, ce qui permet de compter de très grands flux d'éléments.
- La méthode
Exemples d'utilisation
-
Compter tous les éléments d'une liste :
List<String> mots = List.of("pomme", "banane", "orange", "kiwi");long nombreDeMots = mots.stream().count();System.out.println("Nombre de mots : " + nombreDeMots); // Output: Nombre de mots : 4 -
Compter les éléments qui remplissent une condition :
List<Integer> nombres = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);long nombreDeNombresPairs = nombres.stream().filter(n -> n % 2 == 0).count();System.out.println("Nombre de nombres pairs : " + nombreDeNombresPairs); // Output: Nombre de nombres pairs : 5 -
Compter les lignes d'un fichier :
try {long nombreDeLignes = Files.lines(Paths.get("monfichier.txt")).count();System.out.println("Nombre de lignes : " + nombreDeLignes);} catch (IOException e) {e.printStackTrace();}
Points importants
countest une opération simple et efficace pour obtenir le nombre d'éléments dans un flux.- Il est souvent utilisé en combinaison avec d'autres opérations de flux, telles que
filter, pour compter les éléments qui répondent à des critères spécifiques. countest une opération terminale, ce qui signifie qu'elle termine le flux.
Quand utiliser count
- Lorsque vous avez besoin de connaître le nombre total d'éléments dans un flux.
- Lorsque vous devez compter les éléments qui remplissent une condition spécifique.
anyMatch, allMatch, noneMatch
Ces trois opérations terminales vérifient si les éléments d'un stream respectent une condition. Elles retournent toutes un boolean.
| Méthode | Retourne true si... |
|---|---|
anyMatch | au moins un élément respecte la condition |
allMatch | tous les éléments respectent la condition |
noneMatch | aucun élément ne respecte la condition |
List<Integer> nombres = List.of(1, 2, 3, 4, 5);
boolean auMoinsUnPair = nombres.stream().anyMatch(n -> n % 2 == 0); // true
boolean tousPairs = nombres.stream().allMatch(n -> n % 2 == 0); // false
boolean aucunNegatif = nombres.stream().noneMatch(n -> n < 0); // true
Elles s'arrêtent dès que le résultat est déterminé — pas besoin de parcourir tout le stream :
List<String> noms = List.of("Alice", "Bob", "Clara");
boolean existeAvecA = noms.stream().anyMatch(n -> n.startsWith("A")); // true — s'arrête à "Alice"
findFirst
findFirst retourne le premier élément du stream sous forme d'Optional, car le stream pourrait être vide.
List<String> noms = List.of("Alice", "Bob", "Clara");
Optional<String> premier = noms.stream()
.filter(n -> n.startsWith("C"))
.findFirst();
System.out.println(premier.orElse("aucun")); // Clara
Optional<String> absent = noms.stream()
.filter(n -> n.startsWith("Z"))
.findFirst();
System.out.println(absent.orElse("aucun")); // aucun
Règle pratique : utilisez anyMatch pour vérifier une existence, findFirst pour récupérer l'élément lui-même.
Documentation Oracle : forEach, reduce, collect, count, anyMatch, allMatch, noneMatch, findFirst (java.util.stream.Stream) · toList, toSet, toMap, joining, groupingBy (java.util.stream.Collectors)